package com.leyantech.ecp.agenx.common.misc;

import com.leyantech.utility.config.ConfigUtil;
import com.leyantech.utility.statsd.StatsDMetricsReporter;

import com.codahale.metrics.MetricRegistry;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.Builder;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.SlidingWindowType;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import io.github.resilience4j.circuitbreaker.utils.MetricNames;
import io.github.resilience4j.metrics.publisher.CircuitBreakerMetricsPublisher;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;

import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
 * @author eahau, {@literal <eahau@foxmail.com>}
 * @date 2022-12-09.
 */
public class CircuitBreakerFactory {

  static final CircuitBreakerRegistry circuitBreakerRegistry = getCircuitBreakerRegistry();

  static CircuitBreakerRegistry getCircuitBreakerRegistry() {

    final MetricRegistry metricRegistry = new MetricRegistry();

    StatsDMetricsReporter.forRegistry(metricRegistry)
        .prefixedWith(MetricNames.DEFAULT_PREFIX)
        .build()
        .start(10, TimeUnit.SECONDS);

    return CircuitBreakerRegistry.custom()
        .addRegistryEventConsumer(new CircuitBreakerMetricsPublisher("", metricRegistry))
        .build();
  }

  static <T> void safeSet(T value, Consumer<T> consumer) {
    if (value != null) {
      consumer.accept(value);
    }
  }

  static void safeSetExceptions(JsonArray array, Consumer<Class<? extends Throwable>[]> consumer) {

    @SuppressWarnings("unchecked") final Class<? extends Throwable>[] classes = array
        .stream()
        .map(String::valueOf)
        .map(clzName -> {
          try {
            return Class.forName(clzName);
          } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
          }
        })
        .filter(Throwable.class::isAssignableFrom)
        .toArray(Class[]::new);

    safeSet(classes, consumer);
  }

  public static Supplier<CircuitBreaker> circuitBreakerConfig(String name) {
    return ConfigUtil.getDynamicConfig(
        "resilience4j",
        "circuitBreaker." + name,
        json -> {
          final JsonObject jsonObject = new JsonObject(json);

          final Builder builder = CircuitBreakerConfig.custom();

          safeSet(jsonObject.getLong("waitIntervalSeconds"),
              seconds -> builder.waitDurationInOpenState(Duration.ofSeconds(seconds)));

          safeSet(jsonObject.getString("slidingWindowType"),
              type -> builder.slidingWindowType(SlidingWindowType.valueOf(type)));

          safeSet(jsonObject.getInteger("slowCallDurationThreshold"),
              seconds -> builder.slowCallDurationThreshold(Duration.ofSeconds(seconds)));

          safeSet(jsonObject.getInteger("maxWaitDurationSeconds"),
              seconds -> builder.maxWaitDurationInHalfOpenState(Duration.ofSeconds(seconds)));

          safeSet(jsonObject.getFloat("slowCallRateThreshold"), builder::slowCallRateThreshold);

          safeSet(jsonObject.getFloat("failureRateThreshold"), builder::failureRateThreshold);

          safeSet(jsonObject.getInteger("slidingWindowSize"), builder::slidingWindowSize);

          safeSet(jsonObject.getInteger("minimumNumberOfCalls"), builder::minimumNumberOfCalls);

          safeSet(jsonObject.getInteger("permittedNumberOfCallsInHalfOpenState"),
              builder::permittedNumberOfCallsInHalfOpenState);

          safeSet(jsonObject.getBoolean("automaticTransitionFromOpenToHalfOpenEnabled"),
              builder::automaticTransitionFromOpenToHalfOpenEnabled);

          safeSet(jsonObject.getBoolean("writableStackTraceEnabled"),
              builder::writableStackTraceEnabled);

          safeSetExceptions(jsonObject.getJsonArray("recordExceptions"), builder::recordExceptions);

          safeSetExceptions(jsonObject.getJsonArray("ignoreExceptions"), builder::ignoreExceptions);

          final CircuitBreakerConfig config = builder.build();

          return circuitBreakerRegistry.circuitBreaker(name, config);
        },
        circuitBreakerRegistry.circuitBreaker(name, circuitBreakerRegistry.getDefaultConfig())
    );
  }

}
